package cn.edu.sjtu.process_management.service;

import cn.edu.sjtu.process_management.constant.RespStatus;
import cn.edu.sjtu.process_management.dto.ProcessDTO;
import cn.edu.sjtu.process_management.exception.model.BpmnException;
import cn.edu.sjtu.process_management.exception.model.ServiceException;
import cn.edu.sjtu.process_management.model.Process;
import cn.edu.sjtu.process_management.repository.ProcessRepository;
import cn.edu.sjtu.process_management.util.StrConvertUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.*;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaProperties;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaProperty;
import org.dozer.DozerBeanMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author loumoon
 * @date 2019-11-01 13:36
 */
@Service
@Slf4j
public class ProcessService {
    @Autowired
    private ProcessRepository processRepository;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DozerBeanMapper mapper;

    @Value("${microServiceManagement.instanceInquiry.url}")
    private String instanceInquiryUrl; // 微服务管理平台提供的实例查询接口

    /*创建流程*/
    public void createProcess(String name, String owner, String bpmn) {
        // 用户创建了重复的流程名称
        if (processRepository.findProcessByNameAndOwner(name, owner) != null) {
            throw new ServiceException(RespStatus.DUPLICATED_PROCESS_NAME, this.getClass().getName());
        }

        Process process = Process.builder()
                .created(new Date())
                .name(name)
                .bpmn(parseProcessBpmn(bpmn)) // 解析bpmn
                .owner(owner)
                .deprecated(false)
                .build();

        processRepository.save(process);
    }

    /*更新流程bpmn*/
    public void updateProcessBpmn(String name, String owner, String bpmn) {
        Process process = processRepository.findProcessByNameAndOwner(name, owner);
        if (process == null) {
            throw new ServiceException(RespStatus.NON_AUTHORITY_UPDATE_PROCESS, this.getClass().getName());
        }
        log.info(bpmn);
        String parse = parseProcessBpmn(bpmn);
        process.setBpmn(parse); // 解析bpmn
        log.info("解析完毕,更新成功");
        processRepository.save(process);
    }

    /*更新流程状态*/
    public void updateProcessStatus(String name, String owner, boolean status) {
        Process process = processRepository.findProcessByNameAndOwner(name, owner);
        if (process == null) {
            throw new ServiceException(RespStatus.NON_AUTHORITY_UPDATE_PROCESS, this.getClass().getName());
        }
        process.setDeprecated(status);
        processRepository.save(process);
    }

    /*更新流程名称*/
    public void updateProcessName(String oldName, String owner, String newName) {
        Process process = processRepository.findProcessByNameAndOwner(oldName, owner);
        if (process == null) {
            throw new ServiceException(RespStatus.NON_AUTHORITY_UPDATE_PROCESS, this.getClass().getName());
        }
        if (processRepository.findProcessByNameAndOwner(newName, owner) != null) {
            throw new ServiceException(RespStatus.DUPLICATED_PROCESS_NAME, this.getClass().getName());
        }
        process.setName(newName);
        processRepository.save(process);
    }

    /*查找流程*/
    public Process findProcess(String processId) {
        Optional<Process> process = processRepository.findById(processId);
        if (!process.isPresent()) {
            throw new ServiceException(RespStatus.NON_EXIST_PROCESS, this.getClass().getName());
        }
        return process.get();
    }

    public Process findProcess(String processId, String owner) {
        Process process = findProcess(processId);
        if (!process.getOwner().equals(owner)) {
            throw new ServiceException(RespStatus.NON_AUTHORITY_TO_PROCESS, this.getClass().getName());
        }
        return process;
    }

    /*查找拥有的所有流程*/
    public List<ProcessDTO> findOwnedProcesses(String owner) {
        List<Process> processes = processRepository.findProcessesByOwner(owner);
        return processes.stream().map(process -> mapper.map(process, ProcessDTO.class)).collect(Collectors.toList());
    }

    /*查找所有流程*/
    public List<ProcessDTO> findAllProcesses() {
        List<Process> processes = processRepository.findAll();
        return processes.stream().map(process -> mapper.map(process, ProcessDTO.class)).collect(Collectors.toList());
    }

    /*删除流程*/
    public void deleteProcess(String processId) {
        Process process = findProcess(processId);
        processRepository.delete(process);
    }

    public void deleteProcess(String processId, String owner) {
        Process process = findProcess(processId);
        if (!process.getOwner().equals(owner)) {
            throw new ServiceException(RespStatus.NON_AUTHORITY_TO_PROCESS, this.getClass().getName());
        }
        processRepository.delete(process);
    }

    /*解析流程bpmn*/
    private String parseProcessBpmn(String bpmn) {

        BpmnModelInstance instance = Bpmn.readModelFromStream(StrConvertUtil.strToInStream(bpmn)); // 加载bpmn模型

        if (instance.getModelElementsByType(StartEvent.class).size() != 1) { // 开始节点数量不为1
            throw new ServiceException(RespStatus.INCORRECT_START_NODE_NUMBER, this.getClass().getName());
        }

        if (instance.getModelElementsByType(EndEvent.class).size() != 1) { // 结束节点数量不为1
            throw new ServiceException(RespStatus.INCORRECT_END_NODE_NUMBER, this.getClass().getName());
        }

        FlowNode node = instance.getModelElementsByType(StartEvent.class).iterator().next();
        parseNode(node, instance);

        return Bpmn.convertToString(instance);
    }

    private void parseNode(FlowNode node, BpmnModelInstance instance) {
        while (true) {
            if (node instanceof StartEvent) {
                if (node.getOutgoing().size() != 1) { // 开始节点的出度不为1
                    throw new ServiceException(RespStatus.INCORRECT_BPMN_FORMAT, this.getClass().getName());
                }
                node = node.getOutgoing().iterator().next().getTarget();
                if (!(node instanceof Task)) { // 开始节点后的节点不为Task节点
                    throw new ServiceException(RespStatus.INCORRECT_BPMN_FORMAT, this.getClass().getName());
                }
            } else if (node instanceof Task) {
                // 取出扩展属性
                CamundaProperties properties = node.getExtensionElements().getElementsQuery().
                        filterByType(CamundaProperties.class).singleResult();
                Map<String, CamundaProperty> propertiesMap = properties.getCamundaProperties().stream().collect(
                        Collectors.toMap(CamundaProperty::getCamundaName, Function.identity()));

                if (propertiesMap.containsKey("instances")) { // 该节点解析过
                    break;
                }
                // 调用微服务管理模块的接口，查询微服务实例
                String serviceName = propertiesMap.get("serviceName").getCamundaValue();
                ResponseEntity<JSONObject> response = restTemplate.getForEntity(instanceInquiryUrl + serviceName,
                        JSONObject.class);
                JSONArray instances = null;
                try {
                    instances = response.getBody().getJSONArray("data");
                } catch (NullPointerException ex) {
                    log.error("Got NPE while getting json array");
                }

                // 在扩展属性中增加一个保存所有微服务实例的子元素
                CamundaProperty instancesProperty = instance.newInstance(CamundaProperty.class);
                CamundaProperty instanceUrlsProperty = instance.newInstance(CamundaProperty.class);
                instancesProperty.setCamundaName("instances");
                instanceUrlsProperty.setCamundaName("instanceUrls");
                // 每个微服务实例都是一个子元素
                if (instances != null && instances.size() > 0) {
                    for (int i = 0; i < instances.size(); ++i) {
                        CamundaProperty instanceProperty = instance.newInstance(CamundaProperty.class);
                        CamundaProperty instanceUrlProperty = instance.newInstance(CamundaProperty.class);
                        String instanceName = instances.getJSONObject(i).getString("name");
                        String instanceUrl = instances.getJSONObject(i).getString("url");
                        instanceProperty.setCamundaName(instanceName);
                        instanceProperty.setCamundaValue("false"); // 微服务实例默认未选中
                        instanceUrlProperty.setCamundaName(instanceName);
                        instanceUrlProperty.setCamundaValue(instanceUrl);
                        instancesProperty.getDomElement().appendChild(instanceProperty.getDomElement());
                        instanceUrlsProperty.getDomElement().appendChild(instanceUrlProperty.getDomElement());
                    }
                } else {
                    log.error("no instance for: " + serviceName);
                    throw new ServiceException(RespStatus.NON_MICRO_SERVICE_INSTANCE, this.getClass().getName());
                }
                // properties.addChildElement(instancesProperty); // doesn't work
                properties.getDomElement().appendChild(instancesProperty.getDomElement()); // works
                properties.getDomElement().appendChild(instanceUrlsProperty.getDomElement());


                if (node.getOutgoing().size() != 1) { // 任务节点的出度不为1
                    throw new ServiceException(RespStatus.INCORRECT_BPMN_FORMAT, this.getClass().getName());
                }
                node = node.getOutgoing().iterator().next().getTarget();
            } else if (node instanceof ExclusiveGateway) {
                for (SequenceFlow sequenceFlow : node.getOutgoing()) { // gateway的分支没有添加判断值
                    if (sequenceFlow.getName() == null) {
                        throw new ServiceException(RespStatus.NON_VALUE_FOR_GATEWAY_BRANCH, this.getClass().getName());
                    }

                    parseNode(sequenceFlow.getTarget(), instance);
                }
                break;
            } else if (node instanceof EndEvent) {
                break;
            } else {
                throw new ServiceException(RespStatus.INCORRECT_BPMN_FORMAT, this.getClass().getName());
            }
        }
    }
}
